package util;


import java.io.IOException;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.HashMap;

import org.apache.log4j.Logger;




/**
 * String Utility Class This is used to encode passwords programmatically
 *
 * <p>
 * <a h
 * ref="StringUtil.java.html"><i>View Source</i></a>
 * </p>
 * 
 * @author <a href="mailto:matt@raibledesigns.com">Matt Raible</a>
 */
public class StringUtil {

    private final static Logger log = Logger.getLogger(StringUtil.class);
//  ---------------------------------------------------------------------------------------------
	// Constructors
	//---------------------------------------------------------------------------------------------
	/**
	 * Empty constructor.
	 */
	private StringUtil()
	{
	}

	public static boolean isBlank(String param){
		return param == null || param.length() == 0;
	}
	
	
	//---------------------------------------------------------------------------------------------
	// Class Methods
	//---------------------------------------------------------------------------------------------
	/**
	 * Converts a object array into a string separated by the delimiter.
	 * @param objArray the object array to be converted.
	 * @param delimiter the delimiter to use as the separater.
	 * @return The converted string.
	 */
	public static String convertToStr(Object[] objArray, int delimiter)
	{
		if ((objArray == null) || (delimiter <= 0))
			return null;

		StringBuffer resultStr = new StringBuffer(40);

		for (int i = 0; i < objArray.length; i++)
		{
			if (objArray[i] != null)
				resultStr.append(objArray[i]);
			else
				resultStr.append("null");

			if (i != objArray.length - 1) resultStr.append((char) delimiter);
		}

		return resultStr.toString();
	}

	/**
	 * Ensures a string to be of the desired length, adding spaces behind if
	 * necessary, and truncating if it is too long.
	 * @param s the string be be operated on.
	 * @param desiredLength the desired length of the string.
	 * @return The string of the desired length.
	 */
	public static String ensureLength(String s, int desiredLength)
	{
		int len = s.length();

		if (len == desiredLength)
			return s;
		if (len > desiredLength)
			return s.substring(0, desiredLength);

		StringBuffer sb = new StringBuffer(s);
		for (int i = len; i < desiredLength; i++)
			sb.append(' ');

		return sb.toString();
	}

	/**
	 * Escapes string for DB operation. Basically escapes the ' character.
	 * @param value the string to escape.
	 * @return The escaped string.
	 */
	public static String escapeForDB(String value)
	{
		if (value == null)
			return null;

		StringBuffer convertStr = new StringBuffer(value);

		for (int i = 0; i < convertStr.length(); i++)
		{
			if (convertStr.charAt(i) == '\'')
				convertStr.insert(i++, '\'');
		}

		return convertStr.toString();
	}

	/**
	 * Escapes string for JavaScript. Basically escapes the ", ' and / characters.
	 * @param value the string to escape.
	 * @return The escaped string.
	 */
	public static String escapeForJS(String value)
	{
		if (value == null)
			return null;

		StringBuffer convertStr = new StringBuffer(value);

		for (int i = 0; i < convertStr.length(); i++)
		{
			char ch = convertStr.charAt(i);
			if ((ch == '"') || (ch == '\'') || (ch == '\\'))
				convertStr.insert(i++, '\\');
		}

		return convertStr.toString();
	}

	/**
	 * Escapes string for HTML. Basically escapes the special characters in HTML.
	 * @param value the string to escape.
	 * @return The escaped string.
	 */
	public static String escapeForHTML(String value)
	{
		if (value == null)
			return null;

		StringBuffer sb = new StringBuffer(value);

		for (int i = 0; i < sb.length(); i++)
		{
			switch (sb.charAt(i))
			{
				case '\'':
					sb.replace(i, i + 1, "&#39;");
					i = i + 4;
					break;
				case '"':
					sb.replace(i, i + 1, "&#34;");
					i = i + 4;
					break;
				case '<':
					sb.replace(i, i + 1, "&#60;");
					i = i + 4;
					break;
				case '>':
					sb.replace(i, i + 1, "&#62;");
					i = i + 4;
					break;
				case '&':
					sb.replace(i, i + 1, "&#38;");
					i = i + 4;
					break;
				case ' ':
					sb.replace(i, i + 1, "&#32;");
					i = i + 4;
					break;
				default:
					break;
			}
		}
		return sb.toString();
	}

	/**
	 * Convert special characters from HTML like '>' to the original string '&GT'
	 * @param value the string to convert.
	 * @return The converted string.
	 */
	public static String escapeFromHTML(String value)
	{
		if (value == null)
			return null;

		StringBuffer sb = new StringBuffer(value);

		for (int i = 0; i < sb.length(); i++)
		{
			switch (sb.charAt(i))
			{
				case '"':
					sb.replace(i, i + 1, "&QUOT");
					i = i + 4;
					break;
				case '<':
					sb.replace(i, i + 1, "&LT");
					i = i + 4;
					break;
				case '>':
					sb.replace(i, i + 1, "&GT");
					i = i + 4;
					break;
				/*
				case '&':
					//check that the string is not already in corrected format
					if((value.indexOf("&QUOT") == -1) && 
					   (value.indexOf("&LT") == -1) &&
					   (value.indexOf("&GT") == -1) &&
					   (value.indexOf("&AMP") == -1))
					{
					   sb.replace(i, i + 1, "&AMP");
					   i = i + 4;
					}
					break;
				*/
				default:
					break;
			}
		}
		return sb.toString();
	}

	/**
	 * Escapes string for XML. Basically escapes the special characters in XML.
	 * @param value the string to escape.
	 * @return The escaped string.
	 */
	public static String escapeForXML(String value)
	{
		if (value == null)
			return null;

		StringBuffer sb = new StringBuffer(value);

		for (int i = 0; i < sb.length(); i++)
		{
			switch (sb.charAt(i))
			{
				case '\'':
					sb.replace(i, i + 1, "&apos;");
					i = i + 5;
					break;
				case '"':
					sb.replace(i, i + 1, "&quot;");
					i = i + 5;
					break;
				case '<':
					sb.replace(i, i + 1, "&lt;");
					i = i + 3;
					break;
				case '>':
					sb.replace(i, i + 1, "&gt;");
					i = i + 3;
					break;
				case '&':
					sb.replace(i, i + 1, "&amp;");
					i = i + 4;
					break;
				default:
					break;
			}
		}
		return sb.toString();
	}

	/**
	 * Parses a URL path to a relative path beginning with jsp/.
	 * This is required when using RequestDispatcher.
	 * @return String relative URL.
	 */
	public static String getParsedDispatcherURL(String URL)
	{
		if (URL == null) return URL;
		int index = URL.indexOf("jsp/");
		if (index < 0)
		{
			index = URL.lastIndexOf("/");
			index++;
		}
		if (index < 0)
			return URL;
		else
			return URL.substring(index, URL.length());
	}

	/**
	 * A quick way to check whether a value is found in an array of string values.
	 * @param value the value to check.
	 * @param valueList a string array containing the list of values.
	 * @return True of the value is found in the list, false otherwise.
	 */
	public static boolean isValueInList(String value, String[] valueList)
	{
		for (int i = 0; i < valueList.length; i++)
			if (valueList[i].equalsIgnoreCase(value))
				return true;
		return false;
	}

	/**
	 * Packs a string array by eliminating null values and empty strings or white
	 * spaces.
	 * @param array the array to pack.
	 * @return The array with null and empty strings removed.
	 */
	public static String[] packArray(String[] array)
	{
		return packArray(array, false);
	}

	/**
	 * Packs a string array by eliminating null values and empty strings or white
	 * spaces.
	 * @param array the array to pack.
	 * @param removeDuplicates true to remove duplicate values in the array.
	 * @return The array with null and empty strings removed.
	 */
	public static String[] packArray(String[] array, boolean removeDuplicates)
	{
		String nextValue;
		String[] packedArray;

		if (!removeDuplicates)
		{
			ArrayList valueList = new ArrayList(array.length);
			for (int i = 0; i < array.length; i++)
			{
				if ((array[i] == null) || (nextValue = array[i].trim()).length() == 0)
					continue;
				valueList.add(nextValue);
			}

			packedArray = new String[valueList.size()];
			valueList.toArray(packedArray);
		}
		else
		{
			HashMap valueListHm = new HashMap();
			for (int i = 0; i < array.length; i++)
			{
				if ((array[i] == null) || (nextValue = array[i].trim()).length() == 0)
					continue;
				valueListHm.put(nextValue, nextValue);
			}

			packedArray = new String[valueListHm.size()];
			valueListHm.keySet().toArray(packedArray);
		}

		return packedArray;
	}

	public static String[] parseStrArray(String rawStr, char delimiter)
	{
		String[] strList = new String[0];

		if (rawStr != null)
		{
			SimpleStringTokenizer st = new SimpleStringTokenizer(rawStr, delimiter);
			int count = st.countTokens();
			strList = new String[count];

			for (int i = 0; i < count; i++)
				strList[i] = st.nextToken();
		}

		return strList;
	}

	/**
	 * Parses a raw string into a string array using the len as the size of each
	 * value..
	 * @param rawStr the string to be parsed.
	 * @param len the size of each value.
	 * @param trim true to trim the values.
	 * @return Returns the parsed tokens in a string array.
	 */
	public static String[] parseStrArray(String rawStr, int len, boolean trim)
	{
		if (rawStr == null)
			return null;

		int numValues = rawStr.length() / len;
		if ((rawStr.length() % len) > 0)
			numValues++;

		String[] strList = new String[numValues];

		for (int i = 0; i < numValues; i++)
		{
			try
			{
				strList[i] = rawStr.substring(i * len, (i + 1) * len);
			}
			catch (StringIndexOutOfBoundsException sobe)
			{
				strList[i] = rawStr.substring(i * len);
			}

			if (trim)
				strList[i] = strList[i].trim();
		}

		return strList;
	}
	/**
	 * Converts a string into int.
	 * @param s the string to be converted.
	 * @param defaultValue default value in case the conversion fails.
	 * @return int.
	 */
	public static int toInt(String s, int defaultValue)
	{
		try
		{
			return Integer.parseInt(s);
		}
		catch (NumberFormatException ne)
		{
			return defaultValue;
		}
	}

	/**
	 * Appends a field value to StringBuffer.
	 * @param sb StringBuffer object.
	 * @param fieldName Name of field.
	 * @param field field to add
	 */
	public static void toString(StringBuffer sb, String fieldName, Object field)
	{
		if (field != null)
			sb.append("  " + fieldName + "=[" + field.toString() + "]\n");
		else
			sb.append("  " + fieldName + "=[null]\n");
	}

	/**
	 * Appends a field list to StringBuffer.
	 * @param sb StringBuffer object.
	 * @param fieldName Name of field.
	 * @param field field to add
	 */
	public static void toString(StringBuffer sb, String fieldName, Object[] field)
	{
		if (field != null && field.length > 0)
		{
			sb.append("  " + fieldName + "=[" + convertToStr(field, ',') + "]\n");
		}
		else
			sb.append("  " + fieldName + "=[null]\n");
	}

	/**
	 * Wraps text with carriage returns at the specified width. Carriage returns
	 * are inserted at the appropriate space or tab.
	 * @param s the string to be wrapped.
	 * @param width the max width of the allowed.
	 * @return The string wrapped to the width.
	 */
	public static String wordWrap(String s, int width)
	{
		int len = s.length();
		if (len <= width)
			return s;

		int index = width;
		while (index >= 0)
		{
			int c = s.charAt(index);
			if ((c == ' ') || (c == '\t'))
				break;
			index--;
		}

		if (index <= 0)
			index = width;

		return s.substring(0, index).trim() + '\n' +
				wordWrap(s.substring(index).trim(), width);
	}
	
	/**
	 * Convert the file name for search in class path
	 * @param fileName the file name to convert
	 * @return null if the fileName is null, if the fileName start with "/"
	 *         just return it, if not, return "/" + fileName
	 */
	public static String convertNameForStreamSearch(String fileName)
	{
		if(fileName == null || fileName.startsWith("/"))
			return fileName;
		else
			return "/" + fileName;
	}
    //~ Methods ================================================================

    /**
     * Encode a string using algorithm specified in web.xml and return the
     * resulting encrypted password. If exception, the plain credentials
     * string is returned
     *
     * @param password Password or other credentials to use in authenticating
     *        this username
     * @param algorithm Algorithm used to do the digest
     *
     * @return encypted password based on the algorithm.
     */
    public static String encodePassword(String password, String algorithm) {
        byte[] unencodedPassword = password.getBytes();

        MessageDigest md = null;

        try {
            // first create an instance, given the provider
            md = MessageDigest.getInstance(algorithm);
        } catch (Exception e) {
            log.error("Exception: " + e);

            return password;
        }

        md.reset();

        // call the update method one or more times
        // (useful when you don't know the size of your data, eg. stream)
        md.update(unencodedPassword);

        // now calculate the hash
        byte[] encodedPassword = md.digest();

        StringBuffer buf = new StringBuffer();

        for (int i = 0; i < encodedPassword.length; i++) {
            if ((encodedPassword[i] & 0xff) < 0x10) {
                buf.append("0");
            }

            buf.append(Long.toString(encodedPassword[i] & 0xff, 16));
        }

        return buf.toString();
    }

    /**
     * Encode a string using Base64 encoding. Used when storing passwords
     * as cookies.
     *
     * This is weak encoding in that anyone can use the decodeString
     * routine to reverse the encoding.
     *
     * @param str
     * @return String
     */
    public static String encodeString(String str)  {
        sun.misc.BASE64Encoder encoder = new sun.misc.BASE64Encoder();
        return encoder.encodeBuffer(str.getBytes()).trim();
    }

    /**
     * Decode a string using Base64 encoding.
     *
     * @param str
     * @return String
     */
    public static String decodeString(String str) {
        sun.misc.BASE64Decoder dec = new sun.misc.BASE64Decoder();
        try {
            return new String(dec.decodeBuffer(str));
        } catch (IOException io) {
        	throw new RuntimeException(io.getMessage(), io.getCause());
        }
    }
}
